package com.wzdigit.wms.wms.service.transfer;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wzdigit.framework.Shift;
import com.wzdigit.mes.basic.api.BasicSimpleCodeRuleApi;
import com.wzdigit.wms.basic.client.request.transfer.TransferLocationReq;
import com.wzdigit.wms.basic.client.response.Result;
import com.wzdigit.wms.basic.client.response.transfer.LocationResponse;
import com.wzdigit.wms.basic.client.response.transfer.SnLocationResponse;
import com.wzdigit.wms.basic.domain.basic.Location;
import com.wzdigit.wms.basic.domain.basic.Sku;
import com.wzdigit.wms.basic.domain.dtos.inventory.SnListInfoDto;
import com.wzdigit.wms.basic.domain.dtos.inventory.UpdateInventoryInputDto;
import com.wzdigit.wms.basic.domain.dtos.transfer.TransferLocationSnDto;
import com.wzdigit.wms.basic.domain.dtos.trsn.TrsnDtlDto;
import com.wzdigit.wms.basic.domain.dtos.trsn.TrsnDto;
import com.wzdigit.wms.basic.domain.inventory.*;
import com.wzdigit.wms.basic.domain.order.TrsnDtl;
import com.wzdigit.wms.basic.domain.order.TrsnDtlGoods;
import com.wzdigit.wms.basic.domain.order.TrsnHeader;
import com.wzdigit.wms.basic.domain.transfer.MovementDtl;
import com.wzdigit.wms.basic.domain.transfer.MovementDtlGoods;
import com.wzdigit.wms.basic.domain.transfer.MovementHeader;
import com.wzdigit.wms.basic.enums.*;
import com.wzdigit.wms.basic.mapper.core.basic.LocationMapper;
import com.wzdigit.wms.basic.mapper.core.basic.SkuMapper;
import com.wzdigit.wms.basic.mapper.core.basic.WarehouseMapper;
import com.wzdigit.wms.basic.mapper.core.inventory.GoodsLogMapper;
import com.wzdigit.wms.basic.mapper.core.inventory.GoodsMapper;
import com.wzdigit.wms.basic.mapper.core.inventory.GoodsPkgMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.MovementDtlGoodsMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.MovementDtlMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.MovementHeaderMapper;
import com.wzdigit.wms.common.entity.StatusCode;
import com.wzdigit.wms.wms.service.core.GoodsService;
import com.wzdigit.wms.basic.service.code.InventoryService;
import com.wzdigit.wms.wms.service.order.TrsnService;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.BeanUtil;
import net.dreamlu.mica.core.utils.CollectionUtil;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName TransferLocationService
 * @Description 在库模块-移库 业务类
 * @Author LHR-PC
 * @Date 2021/3/22 10:06
 * @Version
 **/
@Service
@Slf4j
public class TransferLocationService {

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private GoodsPkgMapper goodsPkgMapper;

    @Autowired
    private SkuMapper skuMapper;

    @Autowired
    private WarehouseMapper warehouseMapper;

    @Autowired
    private LocationMapper locationMapper;

    @Autowired
    private MovementHeaderMapper movementHeaderMapper;

    @Autowired
    private MovementDtlMapper movementDtlMapper;

    @Autowired
    private MovementDtlGoodsMapper movementDtlGoodsMapper;

    @Lazy
    @Autowired
    private SplitAndMergerService splitAndMergerService;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private InventoryService inventoryService;

    @Value(value = "${order_code.move.code}")
    private String moveRuleCode;

    @Reference(version = "${dubbo.consumers.basic.simple.code.rule.api:1.0.0}", check = false)
    private BasicSimpleCodeRuleApi basicSimpleCodeRuleApi;

    private static final String NOTE_NO = "TRSN_ORDER";

    @Autowired
    private TrsnService trsnService;

    @Autowired
    private GoodsLogMapper goodsLogMapper;

    /**
     * 扫描条码
     *
     * @param sn
     * @param scanType
     * @return
     */
    public SnLocationResponse scanSn(String sn, Integer scanType) {
    	int waitUpCode = GoodsStatusEnum.WAIT_UP.getCode();		//待上架--2
        int hasUpCode = GoodsStatusEnum.HAS_UP.getCode();		//已上架--3
        int waitDowmCode = GoodsStatusEnum.WAIT_DOWM.getCode();	//已下架--4
        
        //根据条码判断条码类型并返回对应货品和包装信息
        TransferLocationSnDto snTypeAndInfo = splitAndMergerService.getSnTypeAndInfo(sn);

        Goods goods = snTypeAndInfo.getGoods();
        
        // 校验条码状态
        int goodsStatus = goods.getStatus().intValue();
        if(goodsStatus != waitUpCode && goodsStatus != hasUpCode && goodsStatus != waitDowmCode) {
        	GoodsStatusEnum goodsStatusEnum = GoodsStatusEnum.getEnumByCode(goodsStatus);
        	String msg = String.format("移库要求SN为[待上架]/[已上架]/[已下架]状态，当前为【%s】状态", goodsStatusEnum.enumName);
            Shift.fatal(StatusCode.BUSINESS_ERR, msg);
        }

        if (goods.getIsHold()!=null){
            if (goods.getIsHold().equals(IsHoldEnum.Yes.code)){
                Shift.fatal("SN["+goods.getGoodsSn()+"]已冻结，无法移库!");
            }
        }
        
        GoodsPkg goodsPkg = snTypeAndInfo.getGoodsPkg();
        Boolean isLeaf = snTypeAndInfo.getIsLeaf(); 						//是否叶子节点
        List<Goods> goodsList = snTypeAndInfo.getGoodsList();
        String goodSn = snTypeAndInfo.getGoodsSn();
        String pkgSn = snTypeAndInfo.getPkgSn();
        int snType = snTypeAndInfo.getSnType(); 							//条码类型，0为goodsSn , 1为pkgSn
        Integer pkgId = snTypeAndInfo.getPkgId();
        Integer pkgLevel = snTypeAndInfo.getPkgLevel(); 					//包装等级，默认 0 货品条码
        Sku sku = snTypeAndInfo.getSku();
        Location location = snTypeAndInfo.getLocation();
        Integer whsId = snTypeAndInfo.getWhsId();
        
        SnLocationResponse snLocationResponse = new SnLocationResponse(); 	//返回实体

        GoodsPkg fatherGoods = null;
        ArrayList<SnListInfoDto> snListInfoDtos = new ArrayList<>();
        SnListInfoDto snListInfoDto = new SnListInfoDto(); 					//明细列表里的自己

        if (pkgId == null) {
            //条码没有pkgID,直接返回条码信息
            snLocationResponse.setPkgNo(goodSn);
            snLocationResponse.setGoodsSn(goodSn);
            snLocationResponse.setSkuCode(sku.getSkuCode());
            snLocationResponse.setSourceLocId(goods.getLocId());
            snLocationResponse.setSourceLocCode(location.getLocCode());
            snLocationResponse.setQty(goods.getQty());
            snLocationResponse.setSnLevel(pkgLevel.toString());
            snLocationResponse.setWhsId(whsId);
            snListInfoDto.setQty(goods.getQty());
            snListInfoDto.setPkgNo(goodSn);
            snListInfoDto.setSkuCode(sku.getSkuCode());
            snListInfoDto.setPoNo(goods.getPoNo());
            snListInfoDto.setSkuDesc(sku.getDescCn());
            snListInfoDto.setLocCode(location.getLocCode());
            snListInfoDto.setLocDesc(location.getLocDesc());
            snListInfoDtos.add(snListInfoDto);
            snLocationResponse.setSnList(snListInfoDtos);
            return snLocationResponse;
        }

        //单台整板通用数据
        snLocationResponse.setSkuCode(sku.getSkuCode());
        snLocationResponse.setSourceLocId(goods.getLocId());
        snLocationResponse.setSourceLocCode(location.getLocCode());
        snLocationResponse.setWhsId(whsId);
        if (snType == 0) {
            //goods条码，上级条码是goods里的pkgId
            fatherGoods = goodsPkgMapper.selectOne(new QueryWrapper<>(new GoodsPkg().setGoodsPkgId(goods.getPkgId())));
        } else {
            //pkg条码，上级条码是goodsPkg里的fatherPkgId
            fatherGoods = goodsPkgMapper.selectOne(new QueryWrapper<>(new GoodsPkg().setGoodsPkgId(goodsPkg.getFatherPkgId())));
        }
        if (fatherGoods != null) {
            snLocationResponse.setFatherPkgSn(fatherGoods.getPkgSn());
            snLocationResponse.setFatherPkgId(fatherGoods.getGoodsPkgId());
        }
        snLocationResponse.setSnLevel(String.valueOf(pkgLevel));

        //单台
        if (scanType == 0) {
            if (snType == 0) {
                //扫的SN
                snListInfoDto.setQty(goods.getQty());
                snListInfoDto.setFatherPkgNo(goodsPkg.getPkgSn());
                snListInfoDto.setPkgNo(goodSn);
                snListInfoDto.setSkuCode(sku.getSkuCode());
                snListInfoDto.setPoNo(goods.getPoNo());
                snListInfoDto.setSkuDesc(sku.getDescCn());
                snListInfoDto.setLocCode(location.getLocCode());
                snListInfoDto.setLocDesc(location.getLocDesc());
                snListInfoDtos.add(snListInfoDto);
                snLocationResponse.setPkgNo(pkgSn);
                snLocationResponse.setGoodsSn(goodSn);
                snLocationResponse.setSkuId(sku.getSkuId());
                snLocationResponse.setQty(goods.getQty());
                snLocationResponse.setSnList(snListInfoDtos);
                snLocationResponse.setFatherPkgSn(fatherGoods == null ? goodsPkg.getPkgSn() : fatherGoods.getPkgSn());
                return snLocationResponse;
            } else if (snType == 1) {
                //扫的pkgSn
                BigDecimal qtySum = goodsList.stream().filter(g -> g.getQty() != null).map(Goods::getQty).reduce((x, y) -> x.add(y)).get();
                snLocationResponse.setQty(qtySum);
                snListInfoDto.setFatherPkgNo(fatherGoods == null ? goodsPkg.getPkgSn() : fatherGoods.getPkgSn());
                snListInfoDto.setPkgNo(goodsPkg.getPkgSn());
                snListInfoDto.setSkuCode(sku.getSkuCode());
                snListInfoDto.setPoNo(goods.getPoNo());
                snListInfoDto.setPkgCategory(goodsPkg.getPkgCategory());
                snListInfoDto.setSkuDesc(sku.getDescCn());
                snListInfoDto.setLocCode(location.getLocCode());
                snListInfoDto.setLocDesc(location.getLocDesc());
                snListInfoDtos.add(snListInfoDto);
                snLocationResponse.setGoodsSn(goodSn);
                snLocationResponse.setPkgNo(pkgSn);
                snLocationResponse.setSkuId(sku.getSkuId());
                snLocationResponse.setFatherPkgSn(fatherGoods == null ? goodsPkg.getPkgSn() : fatherGoods.getPkgSn());
                snLocationResponse.setQty(qtySum);
                snLocationResponse.setSnList(snListInfoDtos);
                return snLocationResponse;
            }
        } else if (scanType == 1) {	//整版
            if (isLeaf) {
                //查找同一层级
                //扫的SN
                if (snType == 0) {
                    //根据pkgId找到同级SN
                    goodsList = goodsMapper.selectList(new QueryWrapper<>(new Goods().setPkgId(pkgId)));
                    if (goodsList.size() == 1) {
                        snLocationResponse.setQty(goods.getQty());
                    } else {
                        BigDecimal qtySumWithPkg = goodsList.stream().filter(g -> g.getQty() != null).map(Goods::getQty).reduce((x, y) -> x.add(y)).get();
                        snLocationResponse.setQty(qtySumWithPkg);
                    }
                    for (Goods goods1 : goodsList) {
                    	// 校验条码状态
                    	goodsStatus = goods1.getStatus().intValue();
                        if(goodsStatus != waitUpCode && goodsStatus != hasUpCode && goodsStatus != waitDowmCode) {
                        	GoodsStatusEnum goodsStatusEnum = GoodsStatusEnum.getEnumByCode(goodsStatus);
                        	String msg = String.format("移库要求SN为[待上架]/[已上架]/[已下架]状态，当前为【%s】状态", goodsStatusEnum.enumName);
                            Shift.fatal(StatusCode.BUSINESS_ERR, msg);
                        }

                        if (goods1.getIsHold()!=null){
                            if (goods1.getIsHold().equals(IsHoldEnum.Yes.code)){
                                Shift.fatal("SN["+goods1.getGoodsSn()+"]已冻结，无法移库!");
                            }
                        }

                    	
                        SnListInfoDto snListInfoDtoSingle = new SnListInfoDto();
                        Sku sku1 = skuMapper.selectOne(new QueryWrapper<>(new Sku().setSkuId(goods1.getSkuId())));
                        snListInfoDtoSingle.setPkgNo(goods1.getGoodsSn());
                        snListInfoDtoSingle.setSkuCode(sku1.getSkuCode());
                        snListInfoDtoSingle.setFatherPkgNo(fatherGoods == null ? goodsPkg.getPkgSn() : fatherGoods.getPkgSn());
                        snListInfoDtoSingle.setPoNo(goods1.getPoNo());
                        snListInfoDtoSingle.setSkuDesc(sku.getDescCn());
                        snListInfoDtoSingle.setLocId(goods1.getLocId());
                        snListInfoDtoSingle.setLocDesc(location.getLocDesc());
                        snListInfoDtoSingle.setLocCode(location.getLocCode());
                        snListInfoDtoSingle.setQty(goods1.getQty());
                        snListInfoDtos.add(snListInfoDtoSingle);
                    }
                    snLocationResponse.setPkgNo(pkgSn);
                    snLocationResponse.setGoodsSn(goodSn);
                    snLocationResponse.setSkuId(sku.getSkuId());
                    snLocationResponse.setSnList(snListInfoDtos);
                    snLocationResponse.setFatherPkgSn(fatherGoods == null ? goodsPkg.getPkgSn() : fatherGoods.getPkgSn());
                    return snLocationResponse;
                } else if (snType == 1) {
                    if (fatherGoods == null) {
                        Shift.fatal(StatusCode.SERVER_NOT_FOUND_ERROR, "该条码已是最顶级包装，查无同层级包装条码，请切换至单台扫描");
                    }
                    //扫的pkgSn
                    //单次扫描同级包装信息
                    List<GoodsPkg> goodsPkgs = goodsPkgMapper.selectList(new QueryWrapper<>(new GoodsPkg().setFatherPkgId(goodsPkg.getFatherPkgId())));
                    if (!CollectionUtil.isEmpty(goodsPkgs) && goodsPkgs.size() != 1) {
                        BigDecimal qtySum = BigDecimal.ZERO;
                        for (GoodsPkg pkg : goodsPkgs) {
                            //同级单个包装条码
                            SnListInfoDto snListInfoDtoSingle = new SnListInfoDto();
                            goodsList = goodsMapper.selectList(new QueryWrapper<>(new Goods().setPkgId(pkg.getGoodsPkgId())));
                            if (CollectionUtil.isEmpty(goodsList) || goodsList.get(0) == null) {
                                continue;
                            }
                            for (Goods goods2 : goodsList) {
                            	// 校验条码状态
                            	goodsStatus = goods2.getStatus().intValue();
                                if(goodsStatus != waitUpCode && goodsStatus != hasUpCode && goodsStatus != waitDowmCode) {
                                	GoodsStatusEnum goodsStatusEnum = GoodsStatusEnum.getEnumByCode(goodsStatus);
                                	String msg = String.format("移库要求SN为[待上架]/[已上架]/[已下架]状态，当前为【%s】状态", goodsStatusEnum.enumName);
                                    Shift.fatal(StatusCode.BUSINESS_ERR, msg);
                                }
                                if (goods2.getIsHold()!=null){
                                    if (goods2.getIsHold().equals(IsHoldEnum.Yes.code)){
                                        Shift.fatal("SN["+goods2.getGoodsSn()+"]已冻结，无法移库!");
                                    }
                                }
							}
                            goods = goodsList.get(0);

                            Sku sku1 = skuMapper.selectOne(new QueryWrapper<>(new Sku().setSkuId(goods.getSkuId())));
                            snListInfoDtoSingle.setPkgNo(pkg.getPkgSn());
                            snListInfoDtoSingle.setSkuCode(sku1.getSkuCode());
                            snListInfoDtoSingle.setPkgCategory(pkg.getPkgCategory());
                            snListInfoDtoSingle.setFatherPkgNo(fatherGoods.getPkgSn());
                            snListInfoDtoSingle.setPoNo(goods.getPoNo());
                            snListInfoDtoSingle.setSkuDesc(sku.getDescCn());
                            snListInfoDtoSingle.setLocId(goods.getLocId());
                            snListInfoDtoSingle.setLocDesc(location.getLocDesc());
                            snListInfoDtoSingle.setLocCode(location.getLocCode());
                            //本层级单个包装下SN数量汇总
                            BigDecimal qtySumWithSinglePkg = goodsList.stream().filter(g -> g.getQty() != null).map(Goods::getQty).reduce((x, y) -> x.add(y)).get();
                            snListInfoDtoSingle.setQty(qtySumWithSinglePkg);
                            qtySum = qtySum.add(qtySumWithSinglePkg);

                            snListInfoDtos.add(snListInfoDtoSingle);
                        }
                        //整版数量
                        BigDecimal qtySumWithPkg = snListInfoDtos.stream().map(SnListInfoDto::getQty).reduce((x, y) -> x.add(y)).get();
                        snLocationResponse.setQty(qtySumWithPkg);
                    }
                }
                BeanUtil.copyNonNull(snLocationResponse, snLocationResponse);
                snLocationResponse.setSnList(snListInfoDtos);
                return snLocationResponse;
            } else {
                // 非叶子节点，走单台逻辑
                snLocationResponse.setQty(goods.getQty());
                snListInfoDto.setFatherPkgNo(fatherGoods == null ? goodsPkg.getPkgSn() : fatherGoods.getPkgSn());
                // snLocationResponse.setQty(goods.getQty()); 总数量，列表数量总和
                snListInfoDto.setPkgNo(goodsPkg.getPkgSn());
                snListInfoDto.setSkuCode(sku.getSkuCode());
                snListInfoDto.setPoNo(goods.getPoNo());
                snListInfoDto.setSkuDesc(sku.getDescCn());
                snListInfoDto.setLocCode(location.getLocCode());
                snListInfoDto.setLocDesc(location.getLocDesc());
                snListInfoDtos.add(snListInfoDto);
                BeanUtil.copyNonNull(snLocationResponse, snLocationResponse);
                snLocationResponse.setSnList(snListInfoDtos);
                return snLocationResponse;
            }
        }
        return null;
    }


    /**
     * 提交(移库)
     *
     * @param transferLocationReq
     * @return
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Result<?> commit(TransferLocationReq transferLocationReq, String loginName) throws Exception {
        BigDecimal qtySum = transferLocationReq.getQtySum();
        List<SnListInfoDto> snListInfoDtos = transferLocationReq.getSnList();
        if (CollectionUtil.isEmpty(snListInfoDtos)) {
            Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "条码列表不能为空");
        }
        String targetLocation = transferLocationReq.getTargetLocation();
        LocationResponse locationResponse = this.scanTargetLoc(targetLocation);
        Location location = new Location();
        BeanUtil.copyNonNull(locationResponse, location);
        Integer isSplit = transferLocationReq.getIsSplit(); //是否拆并板，0做校验，1拆并板
        Integer targetWhsId = transferLocationReq.getTargetWhsId();

        List<String> snList = snListInfoDtos.stream().map(SnListInfoDto::getPkgNo).collect(Collectors.toList());
        List<String> neetSplitPkgSn = new ArrayList<>(); //需要拆板的条码

        if (isSplit == 0) {
            //按照相同上级卡版号分组
            List<SnListInfoDto> collect = snListInfoDtos.stream().filter(s -> s.getFatherPkgNo() != null).collect(Collectors.toList());
            if (!CollectionUtil.isEmpty(collect)) {
                Map<String, List<SnListInfoDto>> gruopByPkgNo = collect.stream().collect(Collectors.groupingBy(SnListInfoDto::getFatherPkgNo));
                Set<String> pkgNoList = gruopByPkgNo.keySet();
                for (Iterator<String> iterator = pkgNoList.iterator(); iterator.hasNext(); ) {
                    List<SnListInfoDto> gruopByPkgNoWithSingle = gruopByPkgNo.get(iterator.next());
                    //每组下面提交的数量
                    BigDecimal qtyWithSingleGroupByCommit = gruopByPkgNoWithSingle.stream().map(SnListInfoDto::getQty).reduce((x, y) -> x.add(y)).get();
                    TransferLocationSnDto snTypeAndInfo = splitAndMergerService.getSnTypeAndInfo(gruopByPkgNoWithSingle.get(0).getFatherPkgNo());
                    List<Goods> goodsList = snTypeAndInfo.getGoodsList();
                    if (CollectionUtil.isEmpty(goodsList)) {
                        Shift.fatal(StatusCode.SERVER_NOT_FOUND_ERROR, "查无货品信息");
                    }
                    BigDecimal qtyWithSingleGroupByAll = goodsList.stream().filter(g -> g.getQty() != null).map(Goods::getQty).reduce((x, y) -> x.add(y)).get();
                    if (qtyWithSingleGroupByCommit.compareTo(qtyWithSingleGroupByAll) != 0) {
                        return new Result<>(false, "存在非整板条码，将先拆板再移库");
                    }
                }
            }
        } else if (isSplit == 1) {
            //按照相同上级卡版号分组
            List<SnListInfoDto> collect = snListInfoDtos.stream().filter(s -> s.getFatherPkgNo() != null).collect(Collectors.toList());
            if (!CollectionUtil.isEmpty(collect)) {
                Map<String, List<SnListInfoDto>> gruopByPkgNo = collect.stream().collect(Collectors.groupingBy(SnListInfoDto::getFatherPkgNo));
                Set<String> pkgNoList = gruopByPkgNo.keySet();
                for (Iterator<String> iterator = pkgNoList.iterator(); iterator.hasNext(); ) {
                    List<SnListInfoDto> gruopByPkgNoWithSingle = gruopByPkgNo.get(iterator.next());
                    //每组下面提交的数量
                    BigDecimal qtyWithSingleGroupByCommit = gruopByPkgNoWithSingle.stream().map(SnListInfoDto::getQty).reduce((x, y) -> x.add(y)).get();
                    TransferLocationSnDto snTypeAndInfo = splitAndMergerService.getSnTypeAndInfo(gruopByPkgNoWithSingle.get(0).getFatherPkgNo());
                    List<Goods> goodsList = snTypeAndInfo.getGoodsList();
                    if (CollectionUtil.isEmpty(goodsList)) {
                        Shift.fatal(StatusCode.SERVER_NOT_FOUND_ERROR, "查无货品信息");
                    }
                    BigDecimal qtyWithSingleGroupByAll = goodsList.stream().filter(g -> g.getQty() != null).map(Goods::getQty).reduce((x, y) -> x.add(y)).get();
                    if (qtyWithSingleGroupByCommit.compareTo(qtyWithSingleGroupByAll) != 0) {
                        for (SnListInfoDto snListInfoDto : gruopByPkgNoWithSingle) {
                            //将需要拆包的条码添加到列表里
                            neetSplitPkgSn.add(snListInfoDto.getPkgNo());
                        }
                    }
                }
            }
        }
        //移库
        transferLocationBatch(snList, location.getLocId(), loginName, 0, targetWhsId);
        //拆板
        if (!CollectionUtil.isEmpty(neetSplitPkgSn)) {
            for (String s : neetSplitPkgSn) {
                splitAndMergerService.splitPkg(s, loginName);
            }
        }
        return new Result<>(true, "操作成功");
    }


    /**
     * 批量移库
     *
     * @param sourceSnList 移库条码
     * @param targetLocId  目标库位
     * @param loginName
     * @param tranType     移库类型，正常移库传0 ，拆并包触发移库传1
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void transferLocationBatch(List<String> sourceSnList, Integer targetLocId, String loginName, int tranType, Integer whsId) throws Exception {
        if (CollectionUtil.isEmpty(sourceSnList)) {
            Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "条码列表不能为空");
        }
        if (tranType == 0) {
            if (targetLocId == null) {
                Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "目标库位不能为空");
            }
        }

        if (whsId == null) {
            whsId = locationMapper.selectWhsIdBylocId(targetLocId);
            if (whsId == null) {
                Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "目标仓库不能为空");
            }
        }
        /**
         * 提交后，根据SN的信息统计，生成移库单表头(WMS_MOVEMENT_HEADER)、
         * 移库单明细(WMS_MOVENMENT_DTL)、
         * 移库单明细货品清单(WMS_MOVEMENT_DTL_GOODS)。
         * 统计的维度：货品、货主、工厂、供应商、库存信息、品质状态、批次号、销售订单、销售订单行、订单号、来源库位、目标库位。
         */
        String moveNote="";
        try {
            //写移库单
            moveNote= this.generateMovementInfo(sourceSnList, targetLocId, loginName, tranType);
            //写库存交易凭证,调两次，一次出一次入
            this.insertTrsn(sourceSnList, loginName, -1);
            this.insertTrsn(sourceSnList, loginName, 1);
        } catch (Exception e) {
            e.printStackTrace();
            Shift.fatal(StatusCode.SERVER_MODIFY_ERROR, "生成移库单失败");
        }

        for (String s : sourceSnList) {
            this.transferLocation(s, targetLocId, loginName, tranType, 1, whsId);
        }

        //TODO ZYF：加个日志
        List<Goods> goodsList = goodsMapper.selectBySNs(sourceSnList);
        Goods goodsUpdate=new Goods();
        GoodsLog goodsLog = new GoodsLog();
        String goodsJson;
        for (Goods goods : goodsList) {
            BeanUtil.copyNonNull(goods, goodsUpdate);
            goodsUpdate.setWhsId(whsId);
            goodsUpdate.setLocId(targetLocId);

            goodsLog.setGoodsSn(goods.getGoodsSn());
            goodsLog.setOpCode(OpCodeEnum.MOVE.getCode());
            goodsLog.setReason("移库,移库单号["+moveNote+"]");
            goodsJson="未更新的:" + JSON.toJSONString(goods)+ ",更新的:"+JSON.toJSONString(goodsUpdate);
            goodsLog.setUpdateContent(goodsJson);
            goodsLog.setOperator(loginName);
            goodsLog.setCreateUser(loginName);
            goodsLog.setCreateTime(new Date());
            goodsLog.setModifyUser(loginName);
            goodsLog.setModifyTime(new Date());
            goodsLogMapper.insert(goodsLog);
        }

    }


    /**
     * 单个移库
     *
     * @param sn
     * @param targetLocId
     * @param loginName
     * @param tranType    移库类型，正常移库传0 ，拆并包触发移库传1
     * @param callType    调用类型，直接调用传0 ，批量移库调用传1 (直接调用要写移库单，批量移库方法调用不写)
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void transferLocation(String sn, Integer targetLocId, String loginName, int tranType, int callType, Integer targetWhsId, Goods... targetGoods) throws Exception {
        if (sn == null) {
            Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "条码不能为空");
        }

        TransferLocationSnDto snTypeAndInfo = null;

        if (tranType == 0) {
            if (targetLocId == null) {
                Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "目标库位不能为空");
            }
            snTypeAndInfo = splitAndMergerService.getSnTypeAndInfo(sn);
        } else {
            snTypeAndInfo = splitAndMergerService.getSnTypeAndInfoWithSplit(sn);
        }
        Integer whsId = snTypeAndInfo.getWhsId();

        Goods goods = snTypeAndInfo.getGoods();
        //设置与目标相同状态
        if (targetGoods != null && targetGoods.length > 0) {
            Goods targetGood = targetGoods[0];
            goods.setStatus(targetGood.getStatus());
        }

        //这开始变NULL

        //如果是直接调用，先写移库单，库存交易凭证
        if (callType == 0) {
            List<String> snList = new ArrayList<>();
            snList.add(goods.getGoodsSn());
            this.generateMovementInfo(snList, targetLocId, loginName, tranType);
            //写库存交易凭证,调两次，一次出一次入
            this.insertTrsn(snList, loginName, -1);
            this.insertTrsn(snList, loginName, 1);
        }

        //记录下源库位 locId
        Integer sourceLocId = goods.getLocId();
        if (!whsId.equals(targetWhsId)) {
            Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "源条码和目标条码必须在同一个仓库");
        }
        goods.setLocId(targetLocId); //修改库位locId 为目标库位
        goods.setModifyUser(loginName);
        goods.setModifyTime(new Date());
        if (targetLocId == null) {
//            goods.setStatus(GoodsStatusEnum.HAS_DOWN.code);
            //XM
            goods.setStatus(GoodsStatusEnum.WAIT_DOWM.code);
        }
        //TODO 此处原本是调【更新货品服务】，但该服务不会update空的字段，目标库位为空时库位不会修改，暂时改成下面方法
        goodsMapper.updateLocIdAndStatusById(goods);//有目标库位为NULL的情况，故LHR使用这种写法
        //注释掉上面那行，原来有的
        boolean b = goodsService.updateGoods(goods);
        if (!b) {
            Shift.fatal(StatusCode.SERVER_MODIFY_ERROR, "调【更新货品服务】更新货品失败");
        }
        //更新货品库位之后调库存更新服务，将源库还原
        goods.setLocId(sourceLocId);
        this.setParamWithTransfer(goods, targetLocId, loginName);
//        try {
//            this.setParamWithTransfer(goods, targetLocId, loginName);
//        } catch (Exception e) {
//            e.printStackTrace();
//            Shift.fatal(StatusCode.SERVER_MODIFY_ERROR, "调【库存更新服务】更新库存失败" + e.getMessage());
//        }
        //TODO 测试代码，测试和投产记得删除
//        int a = 0 / 0;
    }

    /**
     * 生成移库单信息
     *
     * @param sourceSnList
     * @param targetLocId
     * @param loginName
     * @param tranType     移库类型，正常移库传0 ，拆并包触发移库传1
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String generateMovementInfo(List<String> sourceSnList, Integer targetLocId, String loginName, int tranType) {
        /**
         * 提交后，根据SN的信息统计，生成移库单表头(WMS_MOVEMENT_HEADER)、
         * 移库单明细(WMS_MOVENMENT_DTL)、
         * 移库单明细货品清单(WMS_MOVEMENT_DTL_GOODS)。
         * 统计的维度：货品、货主、工厂、供应商、库存信息、品质状态、批次号、销售订单、销售订单行、订单号、来源库位、目标库位。
         */
        String s = sourceSnList.get(0);
        TransferLocationSnDto snTypeAndInfo = splitAndMergerService.getSnTypeAndInfoWithSplit(s);
        Goods goods = snTypeAndInfo.getGoods();


        MovementHeader movementHeader = new MovementHeader();

        String moveNo = basicSimpleCodeRuleApi.GenerateCode(moveRuleCode);
        movementHeader.setMovementNo(moveNo);
        movementHeader.setMovementType("M01");
        movementHeader.setErpMoveType("M01");
//        movementHeader.setReasonCode("");
        movementHeader.setSourceNo("");
        movementHeader.setWhsId(goods.getWhsId());
        movementHeader.setDataSource("");
        movementHeader.setMovementRef1("");
        movementHeader.setMovementRef2("");
        movementHeader.setMovementRef3("");
        movementHeader.setMovementRef4("");
        movementHeader.setMovementRef5("");
        movementHeader.setUdf1("");
        movementHeader.setUdf2("");
        movementHeader.setUdf3("");
        movementHeader.setUdf4("");
        movementHeader.setUdf5("");
        if (tranType == 0) {
            movementHeader.setComments("普通移库");
        } else {
            movementHeader.setComments("拆并包移库");
        }
        movementHeader.setStatus(1);
        movementHeader.setCreateTime(new Date());
        movementHeader.setCreateUser(loginName);
        movementHeaderMapper.insert(movementHeader);
        List<Goods> goodsList = goodsMapper.selectBySNs(sourceSnList);
        /**
         *      * 统计的维度：货品、货主、工厂、供应商、库存信息、品质状态、
         *      * 批次号、销售订单、销售订单行、订单号、来源库位、目标库位。
         */
        Map<String, List<Goods>> collect = goodsList.stream().collect(Collectors.groupingBy(g -> this.spliceGroupingKey(g)));
        Set<String> strings = collect.keySet();
        int i = 1;
        for (Iterator<String> iterator = strings.iterator(); iterator.hasNext(); ) {
            List<Goods> list = collect.get(iterator.next());
            BigDecimal qtySum = list.stream().filter(g -> g.getQty() != null).map(Goods::getQty).reduce((x, y) -> x.add(y)).get();
            Goods goods1 = list.get(0);
            MovementDtl movementDtl = new MovementDtl();
            movementDtl.setMovementHeaderId(movementHeader.getMovementHeaderId());
            movementDtl.setMovementNo(moveNo);
            movementDtl.setMovementLineNo(i++);
            movementDtl.setSourceNo("");
            movementDtl.setSourceLineNo("");
            movementDtl.setSkuId(goods1.getSkuId());
            movementDtl.setOwner(goods1.getOwner());
            movementDtl.setFactory(goods1.getFactory());
            movementDtl.setVendor(goods1.getVendorId());
            movementDtl.setInvAttr(goods1.getInvAttr());
            movementDtl.setQaStatus(goods1.getQaStatus());
            movementDtl.setLotno(goods1.getLotno());
            movementDtl.setSoNo(goods1.getSoNo());
            movementDtl.setSoLineNo(goods1.getSoItem());
            movementDtl.setPoNo(goods1.getPoNo());
            movementDtl.setFmLoc(goods1.getLocId());
            movementDtl.setToLoc(targetLocId);
            movementDtl.setUnit(goods1.getUnit());
            movementDtl.setOrderQty(qtySum);
            movementDtl.setCmpQty(qtySum);
            movementDtl.setDataSource("");
            movementDtl.setUdf1(goods1.getUdf1());
            movementDtl.setUdf2(goods1.getUdf2());
            movementDtl.setUdf3(goods1.getUdf3());
            movementDtl.setUdf4(goods1.getUdf4());
            movementDtl.setUdf5(goods1.getUdf5());
            if (tranType == 0) {
                movementDtl.setComments("普通移库");
            } else {
                movementDtl.setComments("拆并包移库");
            }
            movementDtl.setStatus(1);
            movementDtl.setCreateTime(new Date());
            movementDtl.setCreateUser(loginName);
            movementDtlMapper.insert(movementDtl);
            for (Goods goods2 : list) {
                MovementDtlGoods movementDtlGoods = new MovementDtlGoods();
                movementDtlGoods.setMovementDtlId(movementDtl.getMovementDtlId());
                movementDtlGoods.setGoodsSn(goods2.getGoodsSn());
                movementDtlGoods.setGoodsId(goods2.getGoodsId());
                movementDtlGoods.setQty(goods2.getQty());
                if (tranType == 0) {
                    movementDtlGoods.setComments("普通移库");
                } else {
                    movementDtlGoods.setComments("拆并包移库");
                }
                movementDtlGoods.setStatus(goods2.getStatus());
                movementDtlGoods.setCreateUser(loginName);
                movementDtlGoods.setCreateTime(new Date());
                movementDtlGoodsMapper.insert(movementDtlGoods);
            }
        }

        return moveNo;

    }


    /**
     * 设置批次属性1~12
     *
     * @param obj
     * @param lotno
     */
    private int setLotAttr(Object obj, Lotno lotno) throws Exception {
        int i = 1;
        for (i = 1; i < 13; i++) {
            try {
                Class<?> aClass = obj.getClass();
                Object value = lotno.getClass().getMethod("getLotAttr" + i).invoke(lotno, null);
                Method method = aClass.getMethod("setLotAttr" + i, String.class);
                method.invoke(obj, value);
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }
        return i;
    }

    /**
     * 调库存更新服务设置参数
     */
    public void setParamWithTransfer(Goods goods, Integer targetLocId, String loginName) throws Exception {
        UpdateInventoryInputDto fmInv = new UpdateInventoryInputDto();
        UpdateInventoryInputDto toInv = new UpdateInventoryInputDto();
        InvTrans invTrans = new InvTrans();

        fmInv.setQty(goods.getQty().negate());
        fmInv.setLocId(goods.getLocId());//设置库位
        fmInv.setPreInQty(BigDecimal.ZERO);
        fmInv.setPreOutQty(BigDecimal.ZERO);
        fmInv.setAlocQty(BigDecimal.ZERO);
        fmInv.setPreAlocQty(BigDecimal.ZERO);
        fmInv.setOperator(loginName);
        fmInv.setWhsId(goods.getWhsId());
        fmInv.setOwner(goods.getOwner());
        fmInv.setSkuId(goods.getSkuId());
        fmInv.setFactory(goods.getFactory());
        fmInv.setVendorId(goods.getVendorId());
        fmInv.setLotNo(goods.getLotno());
        fmInv.setInvAttr(goods.getInvAttr());
        fmInv.setQaStatus(goods.getQaStatus());
        fmInv.setUnit(goods.getUnit());
        //增加库区
        if (goods.getLocId() !=null) {
            Location location = locationMapper.selectById(goods.getLocId());
            fmInv.setZoneId(location.getZoneId());
        } else {
            fmInv.setZoneId(null);
        }


        toInv.setQty(goods.getQty());
        toInv.setLocId(targetLocId);//设置库位
        toInv.setPreInQty(BigDecimal.ZERO);
        toInv.setPreOutQty(BigDecimal.ZERO);
        toInv.setAlocQty(BigDecimal.ZERO);
        toInv.setPreAlocQty(BigDecimal.ZERO);
        toInv.setOperator(loginName);
        toInv.setWhsId(goods.getWhsId());
        toInv.setOwner(goods.getOwner());
        toInv.setSkuId(goods.getSkuId());
        toInv.setFactory(goods.getFactory());
        toInv.setVendorId(goods.getVendorId());
        toInv.setLotNo(goods.getLotno());
        toInv.setInvAttr(goods.getInvAttr());
        toInv.setQaStatus(goods.getQaStatus());
        toInv.setUnit(goods.getUnit());

        //增加库区
        if (targetLocId !=null) {
            Location location = locationMapper.selectById(targetLocId);
            toInv.setZoneId(location.getZoneId());
        } else {
            toInv.setZoneId(null);
        }

        InvTrans log = new InvTrans();

        log.setDocType(" ");//TODO
        log.setDocNo(" ");
        log.setDocLineNo(" ");
        log.setTrsnNo(" ");
        log.setTrsnLineNo(String.valueOf(" "));
        log.setStatus(InvTransStatusEnum.FINISHED.getCode());
        log.setTransTime(new Date());
        log.setOperator(loginName);
        log.setTransQty(goods.getQty());
        log.setCreateTime(new Date());
        log.setCreateUser(loginName);

        log.setOpCode(OpCodeEnum.MOVEOFF.getDescription());
        inventoryService.inventoryOut(fmInv, log);
        log.setOpCode(OpCodeEnum.MOVEON.getDescription());
        inventoryService.inventoryIn(toInv, log);
    }


    /**
     * 1、拆并包服务
     * List<源条码>、目标条码
     * 	控制点
     * 1)	List<源条码> 与 目标条码必须在同一个仓库内，并且物料是一样的。
     * 2)	拆并板动作，源条码必须是同等级条码，并且属于目标条码的下级条码，否则无法拆并板。
     * 3)	List<源条码>的条码允许从不同上级包装、不同库位上，但 源条码上级包装不允许等于“目标条码”
     * 	逻辑
     * 把源条码从上级包装条码拆分下来，挂靠在 目标条码的 下面，原条码还有下面条码，也一并挂靠。调 更新包装层级服务 (这个服务未实现，要写好，补充到腾讯文档)
     * 如果涉及到“源条码”与“目标条码”在不同库位的，要执行2、移库服务。移库的数量要统计“源条码”代表的全部数量。
     *
     * 2、移库服务
     * 	入参
     * List<源条码>、目标库位
     * 	控制点
     * 1)	List<源条码> 与 目标条码必须在同一个仓库内。
     * 2)	List<源条码>的库位不能等于目标库位
     * 3)	允许不同物料、不同库位信息同时移库
     *
     * 提交后，根据SN的信息统计，生成移库单表头(WMS_MOVEMENT_HEADER)、
     * 移库单明细(WMS_MOVENMENT_DTL)、
     * 移库单明细货品清单(WMS_MOVEMENT_DTL_GOODS)。
     * 统计的维度：货品、货主、工厂、供应商、库存信息、品质状态、
     * 批次号、销售订单、销售订单行、订单号、来源库位、目标库位。
     *
     * 	逻辑
     * 调更新货品服务更新货品清单表(WMS_GOODS) 的List<源条码>的库位为“目标库位”
     * 调库存更新服务更新库位库存、库区库存表。源库位减少、目标库位增加。
     *
     * 3、	更新包装层级服务
     * 货品包装信息(WMS_GOODS_PKG)、货品清单表(WMS_GOODS)的包装层级关系变更。包装层级关系：货品清单表PKG_ID、货品包装信息的GOODS_PKG_ID。
     */


    /**
     * 统计的维度：货品、货主、工厂、供应商、库存信息、品质状态、
     * 批次号、销售订单、销售订单行、订单号、来源库位、目标库位。
     *
     * @param
     * @return
     */
    public String spliceGroupingKey(Goods goods) {
        return goods.getSkuId() + "#" + goods.getOwner() + "#" + goods.getFactory() + "#" + goods.getVendorId()
                + "#" + goods.getLocId() + "#" + goods.getQaStatus() + "#" + goods.getLotno() + "#" + goods.getSoNo()
                + "#" + goods.getSoItem() + "#" + goods.getPoNo() + "#" + goods.getLocId();
    }

    /**
     * 扫描目标库位
     *
     * @param locNo
     * @return
     */
    public LocationResponse scanTargetLoc(String locNo) {
        Location location = locationMapper.selectOne(new QueryWrapper<>(new Location().setLocCode(locNo)));
        if (location == null) {
            Shift.fatal(StatusCode.SERVER_NOT_FOUND_ERROR, "库位不存在");
        }
        Integer whsId = locationMapper.selectWhsIdBylocId(location.getLocId());
        LocationResponse locationResponse = new LocationResponse();
        BeanUtil.copyNonNull(location, locationResponse);
        locationResponse.setWhsId(whsId);
        return locationResponse;
    }

    /**
     * 写库存交易凭证，移库凭证(T04)
     *
     * @param singleGroupSn
     * @param loginName
     * @param type
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void insertTrsn(List<String> singleGroupSn, String loginName, Integer type) {
        TrsnDto trsnDto = new TrsnDto();
        List<Goods> goodsList = goodsMapper.selectBySNs(singleGroupSn);
        if (CollectionUtil.isEmpty(goodsList)) {
            Shift.fatal(StatusCode.INVALID_MODEL_FIELDS, "条码列表不能为空");
        }
        BigDecimal qty = goodsList.stream().map(Goods::getQty).reduce((x, y) -> x.add(y)).get();


        try {
            //生成凭证号
            String noteNo = this.basicSimpleCodeRuleApi.GenerateCode(this.NOTE_NO);
            Goods goods = goodsMapper.selectOne(new QueryWrapper<>(new Goods().setGoodsSn(singleGroupSn.get(0))));
            log.warn("-------------------------->凭证号，移库 noteNo :{}", noteNo);
            //表头
            TrsnHeader trsnHeader = new TrsnHeader();
            trsnHeader.setNoteNo(noteNo);
            trsnHeader.setNoteType(TrsnNoteTypeEnum.MOVE_STORAGE.getCode());
            trsnHeader.setWhsId(goods.getWhsId());
            trsnHeader.setFactory(goods.getFactory());
            trsnHeader.setVendorId(goods.getVendorId());
            trsnHeader.setFmDoc("/");
            trsnHeader.setDocRef1("/");
            trsnHeader.setDocRef2("/");
            trsnHeader.setDocRef3("/");
            trsnHeader.setDocRef4("/");
            trsnHeader.setDocRef5("/");
            trsnHeader.setComments("/");
            trsnHeader.setStatus(TrsnHeaderStatusEnum.CMP.code);
            trsnHeader.setCreateUser(loginName);
            trsnHeader.setCreateTime(new Date());
            trsnHeader.setOwner(goods.getOwner());
            //            明细
            trsnDto.setTrsnHeader(trsnHeader);
            ArrayList<TrsnDtlDto> trsnDtlDtos = new ArrayList<>();
            TrsnDtlDto trsnDtlDto = new TrsnDtlDto();
            TrsnDtl trsnDtl = new TrsnDtl();
            trsnDtl.setNoteNo(noteNo);
            trsnDtl.setTrsnHeaderId(trsnHeader.getTrsnHeaderId());
            trsnDtl.setQaStatus(goods.getQaStatus());
            trsnDtl.setFmDoc("/");
            trsnDtl.setFmDocLine("");
            trsnDtl.setInvUpdateFlag(type);
            trsnDtl.setUnit(goods.getUnit());
            trsnDtl.setNoteLineNo("1");
            trsnDtl.setLotAttr1("/");
            trsnDtl.setLotAttr2("/");
            trsnDtl.setLotAttr3("/");
            trsnDtl.setLotAttr4("/");
            trsnDtl.setLotAttr5("/");
            trsnDtl.setLotAttr6("/");
            trsnDtl.setLotAttr7("/");
            trsnDtl.setLotAttr8("");
            trsnDtl.setLotAttr9("/");
            trsnDtl.setLotAttr10("/");
            trsnDtl.setLotAttr11("/");
            trsnDtl.setLotAttr12("/");
            trsnDtl.setQty(qty);
            trsnDtl.setInvType("");
            trsnDtl.setStatus(TrsnDtlStatusEnum.CMP.code);
            trsnDtl.setComments("移库");
            trsnDtl.setSkuId(goods.getSkuId());
            trsnDtl.setCreateTime(new Date());
            trsnDtl.setCreateUser(loginName);
            //明细-goods
            ArrayList<TrsnDtlGoods> dtlGoodsArrayList = new ArrayList<>();
            if (!CollectionUtil.isEmpty(singleGroupSn)) {
                for (String sn : singleGroupSn) {
                    Goods g = goodsMapper.selectOne(new QueryWrapper<>(new Goods().setGoodsSn(sn)));
                    TrsnDtlGoods trsnDtlGoods = new TrsnDtlGoods();
                    trsnDtlGoods.setTrsnDtlId(trsnDtl.getTrsnDtlId());
                    trsnDtlGoods.setQaStatus(g.getQaStatus());
                    trsnDtlGoods.setGoodsId(g.getGoodsId());
                    trsnDtlGoods.setGoodsSn(g.getGoodsSn());
                    trsnDtlGoods.setQty(g.getQty());
                    trsnDtlGoods.setComments("移库");
                    trsnDtlGoods.setStatus(TrsnDtlGoodsStatusEnum.RECEIVED.getCode());
                    trsnDtlGoods.setCreateTime(new Date());
                    trsnDtlGoods.setCreateUser(loginName);
                    dtlGoodsArrayList.add(trsnDtlGoods);
                }
                trsnDtlDto.setTrsnDtl(trsnDtl);
                trsnDtlDto.setTrsnDtlGoodsList(dtlGoodsArrayList);
                trsnDtlDtos.add(trsnDtlDto);
                trsnDto.setTrsnDtlDtoList(trsnDtlDtos);
                trsnService.addNewOne(trsnDto);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("-------------->移库:写库存交易凭证失败");
            throw e;
        }
    }
}
